#include "QtTutorialApp4.h"
#include <QPainter>

#ifdef WIN32
#ifdef QtTutorial4_EXPORTS
#define QTTUTORIAL4_API __declspec(dllexport)
#else
#define QTTUTORIAL4_API __declspec(dllimport)
#endif
#else
#define QTTUTORIAL4_API
#endif

extern "C" QTTUTORIAL4_API Blobz::QtBaseApplication* GetInstance()
{
	QtTutorial4::QtTutorial4App* pTutorial = new QtTutorial4::QtTutorial4App();
	return (Blobz::QtBaseApplication*)pTutorial;
}

extern "C" QTTUTORIAL4_API unsigned int GetMajorReleaseVersion()
{
	return NUI_RELEASE_MAJOR_VER;
}

extern "C" QTTUTORIAL4_API unsigned int GetMinorReleaseVersion()
{
	return NUI_RELEASE_MINOR_VER;
}

extern "C" QTTUTORIAL4_API bool IsQtApp()
{
	return true;
}

extern "C" QTTUTORIAL4_API const char* GetAppName()
{
	return "QtTutorial4";
}

namespace QtTutorial4
{
	/**
	* Constructor
	*/
	QtTutorial4App::QtTutorial4App():
	mCurrentScale(1),
	mItem(NULL),
	mScore(0),
	mLivesRemaining(3),
	mpOkButton(NULL),
	mpStartButton(NULL),
	mCurrentState(SubState_Menu),
	mStandardFont("Verdana", 20),
	mMissilesDestroyed(0),
	mWaveNumber(1)
	{
		setFlags(QGraphicsItem::ItemIsFocusable);
		
		// Connect the size change signal to the handling method.
		connect(this, SIGNAL(maximizedChanged(bool)), SLOT(onItemsSizeSetup()));
	}

	/**
	* Destructor
	*/
	QtTutorial4App::~QtTutorial4App()
	{
	}

	/**
	* Load all objects used in game
	*/
	bool QtTutorial4App::onInitialize()
	{
		// load music
		mMusic.load(getApplicationDirectory() + "audio/music.mp3", Blobz::AudioType_Music);
		if(mMusic.isLoaded())
			mMusic.play(true);

		// setup the start button
		mpStartButton = new Blobz::ButtonItem(this);
		mpStartButton->load(getApplicationDirectory() + "button1.png", 0, getApplicationDirectory() + "button1_pressed.png");
		mpStartButton->setFont(mStandardFont);
		mpStartButton->setPos(300,300);
		mpStartButton->setText("Play");
		mpStartButton->setSize(200,200);
		mpStartButton->show();

		// setup the ok button
		mpOkButton = new Blobz::ButtonItem(this);
		mpOkButton->load(getApplicationDirectory() + "button1.png", 0, getApplicationDirectory() + "button1_pressed.png");
		mpOkButton->setFont(mStandardFont);
		mpOkButton->setText("Ok!");
		mpOkButton->setSize(200,200);
		mpOkButton->setPos(getWindowSize().width()/2.0f - mpOkButton->boundingRect().width()/2.0f ,300);
		mpOkButton->hide();
		
		createStarField();
		
		// Connect the signals and buttons to eachother.
		connect(mpStartButton,SIGNAL(clicked()),this,SLOT(startButtonClicked()));
		connect(mpOkButton,SIGNAL(clicked()),this,SLOT(okButtonClicked()));
		return true;
	}

	/**
	* Unload all objects
	*/
	void QtTutorial4App::onDeinitialize()
	{
		mMusic.unload();
	}

	/**
	* Handle app reload
	*/
	void QtTutorial4App::onReload()
	{
	}

	/**
	* Triggered on every frame update
	* \param inFrameTime Time since last update ( in seconds )
	*/
	void QtTutorial4App::onUpdate( float inFrameTime )
	{
		inFrameTime = std::min(inFrameTime, 0.01f);

		switch(mCurrentState)
		{
		case SubState_Menu:
			{
				break;
			}
		case SubState_PlayState:
			{
				// Update EnemyMissiles and check if destroyed with the correct reaction 
				// depending on what caused them to be destroyed.
				std::vector<EnemyMissile*>::iterator enemy_iter = mEnemyMissiles.begin();
				for(;enemy_iter != mEnemyMissiles.end();)
				{
					(*enemy_iter)->update(inFrameTime);

					// If the missile has exploded or hit the ground
					if((*enemy_iter)->exploded() || (*enemy_iter)->hitTarget()) 
					{
						// If it is blown into pieces by a friendly missile, award points
						if((*enemy_iter)->exploded()) 
						{
							mScore += 100;
						}
						else if((*enemy_iter)->hitTarget()) 
						{
							// Else if it hits the ground, remove one life
							mLivesRemaining--;
						}

						missileDestroyed();

						delete (*enemy_iter);
						enemy_iter = mEnemyMissiles.erase(enemy_iter);
					}
					else
						enemy_iter++;
				}
				
				// Update FriendlyMissiles and check if they are exploding, testing them
				// against the EnemyMissiles in that case and finally removing any 
				// FriendlyMissiles that have finished exploding.
				std::vector<FriendlyMissile*>::iterator iter = mMissiles.begin();
				for(;iter != mMissiles.end();)
				{
					(*iter)->update(inFrameTime);

					// While exploding check for contact with enemy missiles
					if((*iter)->isExploding()&& !(*iter)->exploded()) 
					{
						QVector2D fm_pos((*iter)->pos());
						float fm_radius = (*iter)->getRadius() * (*iter)->scale();

						// Compare each friendly missile with each enemy missile for collisions
						std::vector<EnemyMissile*>::iterator e_iter = mEnemyMissiles.begin();
						for(;e_iter != mEnemyMissiles.end();e_iter++) 
						{
							QVector2D em_pos((*e_iter)->pos());
							float em_radius = (*e_iter)->getRadius() * (*e_iter)->scale();

							// So we dont have to use sqrt(), saves some CPU-cycles 
							float max_radius = fm_radius * em_radius + em_radius * fm_radius;
							float distance = (em_pos - fm_pos).lengthSquared();

							// If the enemy missile is caught inside the explosion
							if(distance <= max_radius) 
							{
								(*e_iter)->setExploded(true);
							}
						}
					}

					// If the explosion is done, remove missile
					if((*iter)->exploded()) 
					{
						delete (*iter);
						iter = mMissiles.erase(iter);
					}
					else
						iter++;
				}

				// When the player dies change state and prepare for a new game round
				if(mLivesRemaining == 0) 
				{
					mCurrentState = SubState_GameOver;
					mpOkButton->show();
					resetGame();
				}

				// Creation of missiles moved out from missile loop, since modifying a container while 
				// iterating over will cause invalid iterators
				if(mMissilesToBeAdded.size()> 0) 
				{
					std::vector<EnemyMissile*>::iterator add_iter = mMissilesToBeAdded.begin();
					for(;add_iter != mMissilesToBeAdded.end();add_iter++)
					{
						mEnemyMissiles.push_back((*add_iter));
					}
					mMissilesToBeAdded.clear();
				}
				break;
			}
		case SubState_GameOver:
			{
				break;
			}
		}
	}

	/**
	* Events handler
	* \param inEvent Consist of event information
	* \return bool
	*/
	bool QtTutorial4App::onEvent( QEvent *inEvent )
	{
		return false;
	}	

	/**
	* Triggered when there is a finger down event on the screen
	* \param inData holds the touch information
	*/
	void QtTutorial4App::onFingerDown( const TouchListener::TouchData& inData )
	{
		switch(mCurrentState)
		{
		case SubState_Menu:
			{
				mpStartButton->onFingerDown(inData);
				break;
			}
		case SubState_PlayState:
			{
				createFriendlyMissile(inData);
				break;
			}
		case SubState_GameOver:
			{
				mpOkButton->onFingerDown(inData);
			}
		}
	}

	/**
	* Triggered when there is a finger up event on the screen
	* \param inData holds the touch information
	*/
	void QtTutorial4App::onFingerUp( const TouchListener::TouchData& inData )
	{
		switch(mCurrentState)
		{
		case SubState_Menu:
			{
				mpStartButton->onFingerUp(inData);
				break;
			}
		case SubState_PlayState:
			{
				break;
			}
		case SubState_GameOver:
			{
				mpOkButton->onFingerUp(inData);
				break;
			}
		}
	}

	/**
	* Triggered when there is a finger update event on the screen
	* \param inData holds the touch information
	*/
	void QtTutorial4App::onFingerUpdate( const TouchListener::TouchData& inData )
	{
		switch(mCurrentState)
		{
		case SubState_Menu:
			{
				mpStartButton->onFingerUpdate(inData);
				break;
			}
		case SubState_PlayState:
			{
				break;
			}
		}
	}

	/**
	* Triggered when there is a finger moves out of the screen or application window
	* \param inData holds the touch information
	*/
	void QtTutorial4App::onFingerExit( const TouchListener::TouchData& inData )
	{
		// Treat it same as finger up event
		onFingerUp(inData);
	}

	/**
	* Render method
	* \param inpPainter QT Rendering system pointer
	* \param inpOption Pointer to various rendering options
	* \param inpWidget Pointer to Parent Widget
	*/
	void QtTutorial4App::paint( QPainter* inpPainter, const QStyleOptionGraphicsItem *inpOption, QWidget *inpWidget )
	{
		inpPainter->setPen(Qt::white);
		inpPainter->drawPoints(&mStars.front(),mStars.size());
		inpPainter->fillRect(0,getWindowSize().height()-50,getWindowSize().width(),50,Qt::green);

		switch(mCurrentState)
		{
		case SubState_Menu:
			{
				inpPainter->setPen(Qt::white);
				inpPainter->setFont(mStandardFont);
				QString text("Welcome to Snowflake Command, evil snowflakes are trying to steal your codes and now is the time to defend, click play to start!");
				inpPainter->drawText(QRectF(500*scale(),300*scale(),600,300),text);
				break;
			}
		case SubState_PlayState:
			{
				inpPainter->setPen(Qt::white);
				inpPainter->setFont(mStandardFont);
				inpPainter->drawText(25,25,"Score: " + QString::number(mScore));
				inpPainter->drawText(600,25,"Lives Remaining: " + QString::number(mLivesRemaining));

				break;
			}
		case SubState_GameOver:
			{
				QFont font("Times",40,QFont::Bold);
				QFontMetrics metrics(font);
				QRectF rect = metrics.boundingRect("You lose!");

				inpPainter->setPen(Qt::white);
				inpPainter->setFont(font);

				float position = getWindowSize().width() /2.0f  - rect.width() /2.0f;

				inpPainter->drawText(position,450,"You lose!");
				inpPainter->drawText(position,500,"Score: " + QString::number(mScore));
				break;
			}
		}
	}

	/**
	* Generates the star field in the background
	*/
	void QtTutorial4App::createStarField()
	{
		// Randomizes out new stars each game. Mostly to demonstrate how QGraphicsScene works.
		float width = getWindowSize().width();
		float height = getWindowSize().height();

		for(int i = 0; i < 200; i++)
		{
			float x = Blobz::frand() * width;
			float y = (Blobz::frand() * height) - 50;
			mStars.push_back(QPointF(x,y));
		}
	}	

	/**
	* Resets the game
	*/
	void QtTutorial4App::resetGame()
	{
		mLivesRemaining = 3;
		mScore = 0;
		mWaveNumber = 1;
		mMissilesDestroyed = 0;
		for(std::vector<EnemyMissile*>::iterator it = mMissilesToBeAdded.begin(); it != mMissilesToBeAdded.end(); ++it)
			delete (*it);
		mMissilesToBeAdded.clear();
		for(std::vector<EnemyMissile*>::iterator it = mEnemyMissiles.begin(); it != mEnemyMissiles.end(); ++it)
			delete (*it);
		mEnemyMissiles.clear();
		for(std::vector<FriendlyMissile*>::iterator it = mMissiles.begin(); it != mMissiles.end(); ++it)
			delete (*it);
		mMissiles.clear();
		createEnemyMissile();
	}

	/**
	* On Missle Destroyed handler
	*/
	void QtTutorial4App::missileDestroyed()
	{
		mMissilesDestroyed++;
		if(mMissilesDestroyed >= mWaveNumber)
		{
			mMissilesDestroyed = 0;
			mWaveNumber++;
			for(unsigned int i = 0; i < mWaveNumber;i++)
			{
				createEnemyMissile();
			}
		}
	}

	/**
	* Generate Missles
	*/
	void QtTutorial4App::createEnemyMissile()
	{
		EnemyMissile *missile = new EnemyMissile(this);
		float random_x = Blobz::frand() * getWindowSize().width();
		missile->setPos(QPointF(random_x,0));
		missile->initialize(getApplicationDirectory());
		QVector2D targetPos(Blobz::frand(100*scale(), getWindowSize().width()-100*scale()), getWindowSize().height()-50*scale());
		missile->setTargetPosition(targetPos);
		missile->setScale(scale());

		mMissilesToBeAdded.push_back(missile);
	}

	/**
	* Adds new friendly missile at the touch location
	* \param inData Holds touch location information.
	* \return void
	*/
	void QtTutorial4App::createFriendlyMissile(const TouchListener::TouchData &inData)
	{
		if(mMissiles.size() > mWaveNumber)
			return;

		FriendlyMissile* missile = new FriendlyMissile(this);
		missile->setPos(QPointF(getWindowSize().width()/2.0f,getWindowSize().height()-50));
		missile->initialize(getApplicationDirectory());
		missile->setTargetPosition(inData.toVector2D());
		missile->setScale(scale());

		mMissiles.push_back(missile);
	}

	/**
	* Slot for handling Start Button Pressed event
	*/
	void QtTutorial4App::startButtonClicked()
	{
		createEnemyMissile();
		mCurrentState = SubState_PlayState;
		mpStartButton->hide();
	}

	/**
	* Slot for handling OK Button Pressed Event
	*/
	void QtTutorial4App::okButtonClicked()
	{
		mCurrentState = SubState_Menu;
		mpOkButton->hide();
		mpStartButton->show();
	}

	/**
	* Rescale and reposition items.
	*/
	void QtTutorial4App::onItemsSizeSetup()
	{
		// The factor of scale change between the previous scale and new scale is calculated to be used 
		// for easy transformations.
		float naturalScale = scale();
		float scaleChange = naturalScale / mCurrentScale;

		// Missiles positions and scales need to be altered as well as their target positions in order to 
		// make sure they don't change direction after rescaling.

		for(std::vector<FriendlyMissile*>::iterator it = mMissiles.begin(); it != mMissiles.end(); ++it)
		{
			(*it)->setPos((*it)->pos() * scaleChange);
			(*it)->setTargetPosition((*it)->targetPosition() * scaleChange);
			(*it)->setScale((*it)->scale() * scaleChange);
		}

		for(std::vector<EnemyMissile*>::iterator it = mEnemyMissiles.begin(); it != mEnemyMissiles.end(); ++it)
		{
			(*it)->setPos((*it)->pos() * scaleChange);
			(*it)->setTargetPosition((*it)->targetPosition() * scaleChange);
			(*it)->setScale((*it)->scale() * scaleChange);
		}

		for(std::vector<EnemyMissile*>::iterator it = mMissilesToBeAdded.begin(); it != mMissilesToBeAdded.end(); ++it)
		{
			(*it)->setPos((*it)->pos() * scaleChange);
			(*it)->setTargetPosition((*it)->targetPosition() * scaleChange);
			(*it)->setScale((*it)->scale() * scaleChange);
		}

		if(mpStartButton)
		{
			mpStartButton->setPos(mpStartButton->pos() * scaleChange);
			mpStartButton->setScale(mpStartButton->scale() * scaleChange);
		}

		if(mpOkButton)
		{
			mpOkButton->setPos(mpOkButton->pos() * scaleChange);
			mpOkButton->setScale(mpOkButton->scale() * scaleChange);
		}

		for(std::vector<QPointF>::iterator it = mStars.begin(); it != mStars.end(); ++it)
			(*it) *= scaleChange;

		// Finally the current scale is set to the new value.
		mCurrentScale = naturalScale;
	}
}